package com.sx.pay.service.wepay;

import com.alibaba.fastjson.JSONObject;
import com.sx.pay.model.PayModel;
import com.sx.pay.model.PayType;
import com.sx.pay.model.RefundModel;
import com.sx.pay.model.WepayParams;
import com.sx.pay.service.AbsPayService;
import com.sx.utils.HttpService;
import com.sx.utils.RandomUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.ssl.SSLContexts;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.InputStream;
import java.math.BigDecimal;
import java.security.KeyStore;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @author AdinZ
 * @date 2020/8/28 16:16
 */
@Slf4j
public abstract class AbsWxPayService extends AbsPayService {

    private final WepayParams params;
    private String downloadCaUrl="";
    private OkHttpClient httpClient;


    public AbsWxPayService(WepayParams params) {
        super(params);
        if(params==null)throw new RuntimeException("微信支付参数不能空");
        if(StringUtils.isEmpty(params.getMchId())) throw new RuntimeException("微信支付商户号不能空");
        this.params = params;
    }



    @Override
    public Map createPayOrder(PayModel model){
        final String nonce_str= RandomUtils.randomStr(16);
        SortedMap<Object,Object> paramMap = new TreeMap<>();
        paramMap.put("appid", this.params.getAppId());
        paramMap.put("body", model.getTitle());
        paramMap.put("mch_id", this.params.getMchId());
        paramMap.put("openid", model.getOpenId());
        paramMap.put("nonce_str", nonce_str);
        paramMap.put("notify_url", super.getNotifyUrl());
        paramMap.put("out_trade_no", model.getPayCode());
        paramMap.put("total_fee", model.getPayMoney().multiply(BigDecimal.valueOf(100)).intValue()+"");
        paramMap.put("trade_type", model.getPayType()== PayType.WXPAY_APP ? "APP":
                model.getPayType()==PayType.WXPAY_MINI ? "JSAPI" :
                model.getPayType()==PayType.WXPAY_PUBLIC?"JSAPI":"NATIVE");
        String sign = TenpayUtil.createSign(paramMap, this.params.getApiKey());
        String xmlData = TenpayUtil.getRequestXml(paramMap, sign);
        try {
            String resdata = TenpayUtil.Unifiedorder(xmlData);
            log.info("[微信支付下单] 接口返回数据:{}",resdata);
            Document   dd = DocumentHelper.parseText(resdata);
            String prepayid = "";
            if (dd != null) {
                Element root = dd.getRootElement();
                if(StringUtils.equals(root.elementText("return_code"),"FAIL")){
                    throw new RuntimeException(root.elementText("return_msg"));
                }
                if(StringUtils.equals(root.elementText("result_code"),"FAIL")){
                    throw new RuntimeException(root.elementText("err_code_des"));
                }
                Element prePayId = root.element("prepay_id");
                StringUtils.isEmpty(prePayId.getText());
                prepayid = prePayId.getText();
                Element nonceStr = root.element("nonce_str");
                int time = (int) (System.currentTimeMillis() / 1000L);
                SortedMap<Object,Object> twiceMap = new TreeMap<>();
                twiceMap.put("appId", this.params.getAppId());
                twiceMap.put("nonceStr", nonceStr.getText());
                twiceMap.put("signType", "MD5");
                twiceMap.put("package", "prepay_id=" + prepayid);
                twiceMap.put("timeStamp", String.valueOf(time));
                String twiceSign = TenpayUtil.createSign( twiceMap, this.params.getApiKey());
                twiceMap.put("paySign", twiceSign);
                return twiceMap;
            }else throw new RuntimeException(resdata);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public boolean verify(JSONObject data) {
        SortedMap<Object,Object> paramMap=new TreeMap<>();
        for (String key:data.keySet()){
            paramMap.put(key,data.getString(key));
        }
        String restSign=data.getString("sign");
        String sign = TenpayUtil.createSign(paramMap, this.params.getApiKey());
        log.info("[支付服务] 微信支付-验证回调sign:{},paramSign:{}   全部参数:{}",restSign,sign,data);
        return StringUtils.equalsIgnoreCase(restSign,sign);
    }


    @Override
    public void refund(RefundModel model){
        try {
            SortedMap<Object, Object> paramMap = new TreeMap<>();
            final String nonce_str = RandomUtils.randomStr(16);
            paramMap.put("appid", this.params.getAppId());
            paramMap.put("mch_id", this.params.getMchId());
            paramMap.put("nonce_str", nonce_str);
            if (!StringUtils.isEmpty(model.getTransCode()))
                paramMap.put("transaction_id", model.getTransCode());
            if (!StringUtils.isEmpty(model.getPayCode()))
                paramMap.put("out_trade_no", model.getPayCode());
            paramMap.put("out_refund_no", model.getRefundCode());
            paramMap.put("total_fee", model.getPayFee().multiply(BigDecimal.valueOf(100)).intValue()+"");
//            paramMap.put("refund_account", "REFUND_SOURCE_RECHARGE_FUNDS");
            paramMap.put("refund_fee", model.getRefundFee().multiply(BigDecimal.valueOf(100)).intValue()+"");
//            paramMap.put("op_user_id", this.mchId);
            if (!StringUtils.isEmpty(super.getNotifyUrl()))
                paramMap.put("notify_url", super.getNotifyUrl());
            String sign = TenpayUtil.createSign(paramMap, this.params.getApiKey());
            String xmlData = TenpayUtil.getRequestXml(paramMap, sign);
            String resXml = request(TenpayUtil.WX_REFUND_URL, xmlData);
            log.info("[微信退款] 请求微信接口返回状态 {}  请求参数{}", resXml, xmlData);
            Document dd = DocumentHelper.parseText(resXml);
            if (dd != null) {
                Element root = dd.getRootElement();
                Element returnMsg=root.element("return_msg");
                Element resultCode = root.element("result_code");
                Element errCodeDes = root.element("err_code_des");
                if (resultCode != null) {
                    String code = resultCode.getText();
                    if ("SUCCESS".equals(code.toUpperCase())) {
                        return;
                    } else {
                        throw new RuntimeException(errCodeDes.getText());
                    }
                } else {
                    throw new RuntimeException(returnMsg.getText());
                }
            } else throw new RuntimeException("");
        }catch (Exception ex){
            throw new RuntimeException(ex);
        }
    }

    @Override
    public String getAppId() {
        return params.getAppId();
    }

    private OkHttpClient initHttpClient() throws Exception {
        if (this.httpClient == null&&!StringUtils.equals(this.downloadCaUrl,this.params.getCertUrl())) {
            InputStream stream = null;
            Response response = null;
            try {
                response = HttpService.getInstance().get(new Request.Builder().url(this.params.getCertUrl()));
                if (response.isSuccessful()) {
                    stream = response.body().byteStream();
                }
                if (stream == null) throw new RuntimeException("证书下载失败");
                KeyStore keyStore = KeyStore.getInstance("PKCS12");
                keyStore.load(stream, this.params.getMchId().toCharArray());
                SSLContext context = SSLContexts.custom().loadKeyMaterial(keyStore, this.params.getMchId().toCharArray()).build();
                TrustManagerFactory trustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManager.init(keyStore);
                OkHttpClient.Builder builder = new OkHttpClient.Builder()
                        .sslSocketFactory(context.getSocketFactory(), (X509TrustManager) trustManager.getTrustManagers()[0]);
                this.httpClient = builder.build();
                this.downloadCaUrl=this.params.getCertUrl();
            } finally {
                if (response != null) response.close();
                if(stream!=null) stream.close();
            }
        }
        return this.httpClient;
    }

    private String request(String url,String xmlBody) throws Exception {
        Response response=null;
            Request request = new Request.Builder().url(url).post(RequestBody.create(MediaType.parse("application/xml"), xmlBody)).build();
        try {
            response = initHttpClient().newCall(request).execute();
            if(response.isSuccessful())
                return response.body().string();
            else return null;
        }finally {
            if(response!=null) response.close();
        }
    }


}
